Skip to content
This repository has been archived by the owner on Nov 24, 2018. It is now read-only.

Commit

Permalink
Merge pull request #82 from mmatczak/master
Browse files Browse the repository at this point in the history
Directives enabling valdr validation of non-input/-select/-textarea
  • Loading branch information
philippd committed Jun 22, 2015
2 parents 537120e + 077d0da commit dbbbd37
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 196 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- [url](#url)
- [future / past](#future--past)
- [Adding custom validators](#adding-custom-validators)
- [Applying validation to custom input widgets](#applying-validation-to-custom-input-widgets)
- [Showing validation messages](#showing-validation-messages-with-valdr-messages)
- [Message template](#message-template)
- [CSS to control visibility](#css-to-control-visibility)
Expand Down Expand Up @@ -260,6 +261,29 @@ yourApp.config(function (valdrProvider) {
}
```
## Applying validation to custom input widgets
valdr applies validation to ```<input>```, ```<textarea>``` and ```<select>``` HTML elements automatically if those
elements are within a ```valdr-type``` block and there is ```ng-model``` bound to them. If you implemented your own
input widget or used one provided by a library (e.g. ui-select), you can still benefit from valdr validation applied to
that input widget. All you need is to decorate it with the ```enableValdrValidation``` directive. In addition to this,
if you would like to make use of [validation messages](#showing-validation-messages-with-valdr-messages), add the
```enableValdrMessage``` directive to the input widget:
```html
<form name="yourForm" novalidate valdr-type="Person">
<div valdr-form-group>
<label for="bestFriend">Best Friend</label>
<my-select
id="bestFriend"
name="bestFriend"
ng-model="person.bestFriend"
enable-valdr-validation
enable-valdr-message>
<!-- other my-select elements -->
</my-select>
</div>
</form>
```
## Showing validation messages with ```valdr-messages```
valdr sets the AngularJS validation states like ```$valid```, ```$invalid``` and ```$error``` on all validated form
elements and forms by default. Additional information like the violated constraints and the messages configured in the
Expand Down
172 changes: 89 additions & 83 deletions src/core/valdrFormItem-directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
* This controller is used if no valdrEnabled parent directive is available.
*/
var nullValdrEnabledController = {
isEnabled: function () { return true; }
isEnabled: function () {
return true;
}
};

/**
Expand All @@ -14,93 +16,97 @@ var nullValdrFormGroupController = {
};

/**
* This directive adds validation to all input and select fields which are bound to an ngModel and are surrounded
* by a valdrType directive. To prevent adding validation to specific fields, the attribute 'valdr-no-validate'
* can be added to those fields.
* This directive adds validation to all input and select fields as well as to explicitly enabled elements which are
* bound to an ngModel and are surrounded by a valdrType directive. To prevent adding validation to specific fields,
* the attribute 'valdr-no-validate' can be added to those fields.
*/
var valdrFormItemDirectiveDefinition =
['valdrEvents', 'valdr', 'valdrUtil', 'valdrClasses', function (valdrEvents, valdr, valdrUtil) {
return {
restrict: 'E',
require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup', '?^valdrEnabled'],
link: function (scope, element, attrs, controllers) {

var valdrTypeController = controllers[0],
ngModelController = controllers[1],
valdrFormGroupController = controllers[2] || nullValdrFormGroupController,
valdrEnabled = controllers[3] || nullValdrEnabledController,
valdrNoValidate = attrs.valdrNoValidate,
fieldName = attrs.name;

/**
* Don't do anything if
* - this is an <input> that's not inside of a valdr-type block
* - there is no ng-model bound to input
* - there is the 'valdr-no-validate' attribute present
*/
if (!valdrTypeController || !ngModelController || angular.isDefined(valdrNoValidate)) {
return;
}

valdrFormGroupController.addFormItem(ngModelController);

if (valdrUtil.isEmpty(fieldName)) {
throw new Error('Form element with ID "' + attrs.id + '" is not bound to a field name.');
}

var updateNgModelController = function (validationResult) {

if (valdrEnabled.isEnabled()) {
var validatorTokens = ['valdr'];

// set validity state for individual valdr validators
angular.forEach(validationResult.validationResults, function (result) {
var validatorToken = valdrUtil.validatorNameToToken(result.validator);
ngModelController.$setValidity(validatorToken, result.valid);
validatorTokens.push(validatorToken);
});

// set overall validity state of this form item
ngModelController.$setValidity('valdr', validationResult.valid);
ngModelController.valdrViolations = validationResult.violations;

// remove errors for valdr validators which no longer exist
angular.forEach(ngModelController.$error, function (value, validatorToken) {
if (validatorTokens.indexOf(validatorToken) === -1 && valdrUtil.startsWith(validatorToken, 'valdr')) {
ngModelController.$setValidity(validatorToken, true);
}
});
} else {
angular.forEach(ngModelController.$error, function (value, validatorToken) {
if (valdrUtil.startsWith(validatorToken, 'valdr')) {
ngModelController.$setValidity(validatorToken, true);
}
});
ngModelController.valdrViolations = undefined;
var valdrFormItemDirectiveDefinitionFactory = function (restrict) {
return ['valdrEvents', 'valdr', 'valdrUtil', function (valdrEvents, valdr, valdrUtil) {
return {
restrict: restrict,
require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup', '?^valdrEnabled'],
link: function (scope, element, attrs, controllers) {

var valdrTypeController = controllers[0],
ngModelController = controllers[1],
valdrFormGroupController = controllers[2] || nullValdrFormGroupController,
valdrEnabled = controllers[3] || nullValdrEnabledController,
valdrNoValidate = attrs.valdrNoValidate,
fieldName = attrs.name;

/**
* Don't do anything if
* - this is an <input> that's not inside of a valdr-type block
* - there is no ng-model bound to input
* - there is the 'valdr-no-validate' attribute present
*/
if (!valdrTypeController || !ngModelController || angular.isDefined(valdrNoValidate)) {
return;
}
};

var validate = function (modelValue) {
var validationResult = valdr.validate(valdrTypeController.getType(), fieldName, modelValue);
updateNgModelController(validationResult);
return valdrEnabled.isEnabled() ? validationResult.valid : true;
};

ngModelController.$validators.valdr = validate;
valdrFormGroupController.addFormItem(ngModelController);

scope.$on(valdrEvents.revalidate, function () {
validate(ngModelController.$modelValue);
});
if (valdrUtil.isEmpty(fieldName)) {
throw new Error('Form element with ID "' + attrs.id + '" is not bound to a field name.');
}

scope.$on('$destroy', function () {
valdrFormGroupController.removeFormItem(ngModelController);
});
var updateNgModelController = function (validationResult) {

if (valdrEnabled.isEnabled()) {
var validatorTokens = ['valdr'];

// set validity state for individual valdr validators
angular.forEach(validationResult.validationResults, function (result) {
var validatorToken = valdrUtil.validatorNameToToken(result.validator);
ngModelController.$setValidity(validatorToken, result.valid);
validatorTokens.push(validatorToken);
});

// set overall validity state of this form item
ngModelController.$setValidity('valdr', validationResult.valid);
ngModelController.valdrViolations = validationResult.violations;

// remove errors for valdr validators which no longer exist
angular.forEach(ngModelController.$error, function (value, validatorToken) {
if (validatorTokens.indexOf(validatorToken) === -1 && valdrUtil.startsWith(validatorToken, 'valdr')) {
ngModelController.$setValidity(validatorToken, true);
}
});
} else {
angular.forEach(ngModelController.$error, function (value, validatorToken) {
if (valdrUtil.startsWith(validatorToken, 'valdr')) {
ngModelController.$setValidity(validatorToken, true);
}
});
ngModelController.valdrViolations = undefined;
}
};

var validate = function (modelValue) {
var validationResult = valdr.validate(valdrTypeController.getType(), fieldName, modelValue);
updateNgModelController(validationResult);
return valdrEnabled.isEnabled() ? validationResult.valid : true;
};

ngModelController.$validators.valdr = validate;

scope.$on(valdrEvents.revalidate, function () {
validate(ngModelController.$modelValue);
});

scope.$on('$destroy', function () {
valdrFormGroupController.removeFormItem(ngModelController);
});

}
};
}];
}
};
}];
},
valdrFormItemElementDirectiveDefinition = valdrFormItemDirectiveDefinitionFactory('E'),
valdrFormItemAttributeDirectiveDefinition = valdrFormItemDirectiveDefinitionFactory('A');

angular.module('valdr')
.directive('input', valdrFormItemDirectiveDefinition)
.directive('select', valdrFormItemDirectiveDefinition)
.directive('textarea', valdrFormItemDirectiveDefinition);
.directive('input', valdrFormItemElementDirectiveDefinition)
.directive('select', valdrFormItemElementDirectiveDefinition)
.directive('textarea', valdrFormItemElementDirectiveDefinition)
.directive('enableValdrValidation', valdrFormItemAttributeDirectiveDefinition);
21 changes: 20 additions & 1 deletion src/core/valdrFormItem-directive.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ describe('valdrFormItem directive', function () {
'</div>' +
'</form>';

var inputWidgetTemplate =
'<form name="demoForm">' +
'<div valdr-type="TestClass">' +
'<section name="fieldName" ng-model="myObject.field" enable-valdr-validation>' +
'</section>' +
'</div>' +
'</form>';

// TEST UTILITIES

function compileTemplate(template) {
Expand Down Expand Up @@ -213,4 +221,15 @@ describe('valdrFormItem directive', function () {

});

});
describe('on explicitly enabled elements', function () {

beforeEach(function () {
compileTemplate(inputWidgetTemplate);
ngModelController = element.find('section').controller('ngModel');
});

runFormItemCommonTests();

});

});
Loading

0 comments on commit dbbbd37

Please sign in to comment.