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

Commit

Permalink
feat(core): Introduce valdr-enabled directive
Browse files Browse the repository at this point in the history
Allows to dynamically enable and disable the validation with valdr for one or multiple form elements.
closes #54, fixes #55
  • Loading branch information
philippd committed Dec 23, 2014
1 parent 9b394d8 commit 48de3c4
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 23 deletions.
3 changes: 2 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ module.exports = function (grunt) {
'src/core/valdr-service.js',
'src/core/valdrFormGroup-directive.js',
'src/core/valdrType-directive.js',
'src/core/valdrFormItem-directive.js'
'src/core/valdrFormItem-directive.js',
'src/core/valdrEnabled-directive.js'
],

message: [
Expand Down
26 changes: 22 additions & 4 deletions demo/core/simple.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
<!DOCTYPE html>
<html>

<head>
<title>Demo</title>
</head>

<body ng-app="demoApp">

<div ng-controller="TestController">

<h1>Simple Demo</h1>

<form name="demoForm" novalidate>
<form name="demoForm" novalidate valdr-enabled="isValdrEnabled">

<div valdr-type="Person">

<h4>Person</h4>
<div valdr-form-group>
<div valdr-form-group valdr-enabled="true">
<label for="lastName">Last Name</label>
<input type="text"
id="lastName"
Expand All @@ -32,7 +34,6 @@ <h4>Person</h4>
<span>$valid {{ demoForm.firstName.$valid }}</span>
</div>


<div valdr-form-group>
<label for="age">Age</label>
<input type="number"
Expand Down Expand Up @@ -96,7 +97,17 @@ <h4>Address</h4>

</form>

<h3>Settings</h3>

<button ng-click="addAddressConstraints()">Add address constraints</button>

<div>
<label for="enabled">Enable/Disable valdr (except last name)</label>
<input id="enabled" type="checkbox" ng-model="isValdrEnabled">
</div>

<button ng-click="removeConstraints()">Remove all constraints</button>

<pre>demoForm.$valid: {{ demoForm.$valid }}</pre>

<h3>demoForm</h3>
Expand All @@ -108,7 +119,7 @@ <h3>constraints</h3>
</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="../js/valdr.min.js"></script>
<script src="../js/valdr.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
Expand Down Expand Up @@ -165,12 +176,19 @@ <h3>constraints</h3>

demoApp.controller('TestController', function ($scope, valdr) {
$scope.person = {};

$scope.genders = [
{ label: 'Male', value: 1 },
{ label: 'Female', value: 2 }
];
$scope.constraints = valdr.getConstraints();

$scope.isValdrEnabled = true;

$scope.removeConstraints = function () {
valdr.removeConstraints('Person');
};

$scope.addAddressConstraints = function () {

valdr.addConstraints({
Expand Down
29 changes: 29 additions & 0 deletions src/core/valdrEnabled-directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
angular.module('valdr')

/**
* This directive allows to dynamically enable and disable the validation with valdr.
* All form elements in a child node of an element with the 'valdr-enabled' directive will be affected by this.
*
* Usage:
*
* <div valdr-enabled="isValidationEnabled()">
* <input type="text" name="name" ng-model="mymodel.field">
* </div>
*
* If multiple valdr-enabled directives are nested, the one nearest to the validated form element
* will take precedence.
*/
.directive('valdrEnabled', ['valdrEvents', function (valdrEvents) {
return {
controller: ['$scope', '$attrs', function($scope, $attrs) {
$scope.$watch($attrs.valdrEnabled, function () {
$scope.$broadcast(valdrEvents.revalidate);
});

this.isEnabled = function () {
var evaluatedExpression = $scope.$eval($attrs.valdrEnabled);
return evaluatedExpression === undefined ? true : evaluatedExpression;
};
}]
};
}]);
110 changes: 110 additions & 0 deletions src/core/valdrEnabled-directive.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
describe('valdrEnabled directive', function () {

// VARIABLES

var $scope, $compile, element, valdr, ngModelController, template,
personConstraints = {
'Person': {
'firstName': {
'required': {
'message': 'first name is required'
}
}
}
};

// TEST UTILITIES

function compileValdrEnabledTemplate() {
element = $compile(angular.element(template))($scope);
$scope.$digest();
ngModelController = element.find('input').controller('ngModel');
}

beforeEach(function () {
module('valdr');
});

beforeEach(inject(function ($rootScope, _$compile_, _valdr_) {
$compile = _$compile_;
valdr = _valdr_;

template =
'<form valdr-type="Person" valdr-enabled="isValdrEnabled()">' +
'<input type="text" name="firstName" ng-model="person.firstName">' +
'</form>';

$scope = $rootScope.$new();

$scope.person = { };
$scope.valdrEnabled = true;
$scope.isValdrEnabled = function () {
return $scope.valdrEnabled;
};

valdr.addConstraints(personConstraints);
}));

// TEST

it('should validate if valdrEnabled is true', function () {
// given
$scope.valdrEnabled = true;

// when
compileValdrEnabledTemplate();

// then
expect(ngModelController.$valid).toBe(false);
});

it('should not validate if valdrEnabled is false', function () {
// given
$scope.valdrEnabled = false;

// when
compileValdrEnabledTemplate();

// then
expect(ngModelController.$valid).toBe(true);
});

it('should validate if no expression is provided', function () {
// given
template =
'<form valdr-type="Person" valdr-enabled>' +
'<input type="text" name="firstName" ng-model="person.firstName">' +
'</form>';

// when
compileValdrEnabledTemplate();

// then
expect(ngModelController.$valid).toBe(false);
});

it('should remove errors when constraints are removed', function () {
// given
compileValdrEnabledTemplate();

// when
valdr.removeConstraints('Person');

// then
expect(ngModelController.$valid).toBe(true);
});

it('should remove errors when valdr gets disabled', function () {
// given
compileValdrEnabledTemplate();

// when
$scope.$apply(function () {
$scope.valdrEnabled = false;
});

// then
expect(ngModelController.$valid).toBe(true);
});

});
68 changes: 50 additions & 18 deletions src/core/valdrFormItem-directive.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/**
* This controller is used if no valdrEnabled parent directive is available.
*/
var nullValdrEnabledController = {
isEnabled: function () { return true; }
};

/**
* This controller is used if no valdrFormGroup parent directive is available.
*/
var nullValdrFormGroupController = {
addFormItem: angular.noop,
removeFormItem: angular.noop
};

/**
* 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'
Expand All @@ -7,12 +22,13 @@ var valdrFormItemDirectiveDefinition =
['valdrEvents', 'valdr', 'valdrUtil', 'valdrClasses', function (valdrEvents, valdr, valdrUtil) {
return {
restrict: 'E',
require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup'],
require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup', '?^valdrEnabled'],
link: function (scope, element, attrs, controllers) {

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

Expand All @@ -26,30 +42,48 @@ var valdrFormItemDirectiveDefinition =
return;
}

if (valdrFormGroupController) {
valdrFormGroupController.addFormItem(ngModelController);
}
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) {
// set validity state for individual validators
angular.forEach(validationResult.validationResults, function (result) {
var validatorToken = valdrUtil.validatorNameToToken(result.validator);
ngModelController.$setValidity(validatorToken, result.valid);
});

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

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 && validatorToken.lastIndexOf('valdr', 0) === 0) {
ngModelController.$setValidity(validatorToken, true);
}
});
} else {
angular.forEach(ngModelController.$error, function (value, validatorToken) {
if (validatorToken.lastIndexOf('valdr', 0) === 0) {
ngModelController.$setValidity(validatorToken, true);
}
});
ngModelController.valdrViolations = undefined;
}
};

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

ngModelController.$validators.valdr = validate;
Expand All @@ -59,9 +93,7 @@ var valdrFormItemDirectiveDefinition =
});

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

}
Expand Down

0 comments on commit 48de3c4

Please sign in to comment.