From dbbd29e3fdec0e04507349ec472644c9a07b8d9a Mon Sep 17 00:00:00 2001 From: Marek Matczak Date: Mon, 15 Jun 2015 09:57:04 +0200 Subject: [PATCH 1/3] Directives enabling valdr validation of non-input/-select/-textarea elements --- src/core/valdrFormItem-directive.js | 172 +++++++++-------- src/core/valdrFormItem-directive.spec.js | 21 ++- src/message/valdrMessage-directive.js | 229 ++++++++++++----------- 3 files changed, 226 insertions(+), 196 deletions(-) diff --git a/src/core/valdrFormItem-directive.js b/src/core/valdrFormItem-directive.js index 6bfff1f..59685ed 100644 --- a/src/core/valdrFormItem-directive.js +++ b/src/core/valdrFormItem-directive.js @@ -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; + } }; /** @@ -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 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 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); diff --git a/src/core/valdrFormItem-directive.spec.js b/src/core/valdrFormItem-directive.spec.js index 9bb7b41..7e2d21b 100644 --- a/src/core/valdrFormItem-directive.spec.js +++ b/src/core/valdrFormItem-directive.spec.js @@ -32,6 +32,14 @@ describe('valdrFormItem directive', function () { '' + ''; + var inputWidgetTemplate = + '
' + + '
' + + '
' + + '
' + + '
' + + '
'; + // TEST UTILITIES function compileTemplate(template) { @@ -213,4 +221,15 @@ describe('valdrFormItem directive', function () { }); -}); \ No newline at end of file + describe('on explicitly enabled elements', function () { + + beforeEach(function () { + compileTemplate(inputWidgetTemplate); + ngModelController = element.find('section').controller('ngModel'); + }); + + runFormItemCommonTests(); + + }); + +}); diff --git a/src/message/valdrMessage-directive.js b/src/message/valdrMessage-directive.js index 9a2b5ba..179f986 100644 --- a/src/message/valdrMessage-directive.js +++ b/src/message/valdrMessage-directive.js @@ -6,41 +6,45 @@ * To prevent adding messages to specific input fields, the attribute 'valdr-no-message' can be added to those input * or select fields. The valdr-message directive is used to do the actual rendering of the violation messages. */ -var valdrMessageDirectiveDefinition = ['$compile', 'valdrUtil', function ($compile) { - return { - restrict: 'E', - require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup'], - link: function (scope, element, attrs, controllers) { - - var valdrTypeController = controllers[0], - ngModelController = controllers[1], - valdrFormGroupController = controllers[2], - valdrNoValidate = attrs.valdrNoValidate, - valdrNoMessage = attrs.valdrNoMessage, - fieldName = attrs.name; - - /** - * Don't do anything if - * - this is an that's not inside of a valdr-type or valdr-form-group block - * - there is no ng-model bound to input - * - there is a 'valdr-no-validate' or 'valdr-no-message' attribute present - */ - if (!valdrTypeController || !valdrFormGroupController || !ngModelController || - angular.isDefined(valdrNoValidate) || angular.isDefined(valdrNoMessage)) { - return; - } - - var valdrMessageElement = angular.element(''); - $compile(valdrMessageElement)(scope); - valdrFormGroupController.addMessageElement(ngModelController, valdrMessageElement); +var valdrMessageDirectiveDefinitionFactory = function (restrict) { + return ['$compile', function ($compile) { + return { + restrict: restrict, + require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup'], + link: function (scope, element, attrs, controllers) { - scope.$on('$destroy', function () { - valdrFormGroupController.removeMessageElement(ngModelController); - }); + var valdrTypeController = controllers[0], + ngModelController = controllers[1], + valdrFormGroupController = controllers[2], + valdrNoValidate = attrs.valdrNoValidate, + valdrNoMessage = attrs.valdrNoMessage, + fieldName = attrs.name; + + /** + * Don't do anything if + * - this is an that's not inside of a valdr-type or valdr-form-group block + * - there is no ng-model bound to input + * - there is a 'valdr-no-validate' or 'valdr-no-message' attribute present + */ + if (!valdrTypeController || !valdrFormGroupController || !ngModelController || + angular.isDefined(valdrNoValidate) || angular.isDefined(valdrNoMessage)) { + return; + } + + var valdrMessageElement = angular.element(''); + $compile(valdrMessageElement)(scope); + valdrFormGroupController.addMessageElement(ngModelController, valdrMessageElement); + + scope.$on('$destroy', function () { + valdrFormGroupController.removeMessageElement(ngModelController); + }); - } - }; -}]; + } + }; + }]; + }, + valdrMessageElementDirectiveDefinition = valdrMessageDirectiveDefinitionFactory('E'), + valdrMessageAttributeDirectiveDefinition = valdrMessageDirectiveDefinitionFactory('A'); var nullValdrType = { @@ -48,97 +52,98 @@ var nullValdrType = { }; angular.module('valdr') - .directive('input', valdrMessageDirectiveDefinition) - .directive('select', valdrMessageDirectiveDefinition) - .directive('textarea', valdrMessageDirectiveDefinition) + .directive('input', valdrMessageElementDirectiveDefinition) + .directive('select', valdrMessageElementDirectiveDefinition) + .directive('textarea', valdrMessageElementDirectiveDefinition) + .directive('enableValdrMessage', valdrMessageAttributeDirectiveDefinition) /** * The valdr-message directive is responsible for the rendering of violation messages. The template used for rendering * is defined in the valdrMessage service where it can be overridden or a template URL can be configured. */ .directive('valdrMessage', - ['$rootScope', '$injector', 'valdrMessage', 'valdrUtil', function ($rootScope, $injector, valdrMessage, valdrUtil) { - return { - replace: true, - restrict: 'A', - scope: { - formFieldName: '@valdrMessage' - }, - templateUrl: function () { - return valdrMessage.templateUrl; - }, - require: ['^form', '?^valdrType'], - link: function (scope, element, attrs, controllers) { - var formController = controllers[0], - valdrTypeController = controllers[1] || nullValdrType; - - var updateTranslations = function () { - if (valdrMessage.translateAvailable && angular.isArray(scope.violations)) { - angular.forEach(scope.violations, function (violation) { - valdrMessage.$translate(valdrMessage.fieldNameKeyGenerator(violation)).then(function (translation) { - violation.fieldName = translation; - }); + ['$rootScope', '$injector', 'valdrMessage', 'valdrUtil', function ($rootScope, $injector, valdrMessage, valdrUtil) { + return { + replace: true, + restrict: 'A', + scope: { + formFieldName: '@valdrMessage' + }, + templateUrl: function () { + return valdrMessage.templateUrl; + }, + require: ['^form', '?^valdrType'], + link: function (scope, element, attrs, controllers) { + var formController = controllers[0], + valdrTypeController = controllers[1] || nullValdrType; + + var updateTranslations = function () { + if (valdrMessage.translateAvailable && angular.isArray(scope.violations)) { + angular.forEach(scope.violations, function (violation) { + valdrMessage.$translate(valdrMessage.fieldNameKeyGenerator(violation)).then(function (translation) { + violation.fieldName = translation; }); - } + }); + } + }; + + var createViolation = function (validatorName) { + var typeName = valdrTypeController.getType(), + fieldName = scope.formFieldName; + + return { + type: typeName, + field: fieldName, + validator: validatorName, + message: valdrMessage.getMessage(typeName, fieldName, validatorName) }; + }; - var createViolation = function (validatorName) { - var typeName = valdrTypeController.getType(), - fieldName = scope.formFieldName; - - return { - type: typeName, - field: fieldName, - validator: validatorName, - message: valdrMessage.getMessage(typeName, fieldName, validatorName) - }; - }; + var addViolationsToScope = function () { + scope.violations = []; - var addViolationsToScope = function () { - scope.violations = []; + angular.forEach(scope.formField.valdrViolations, function (violation) { + scope.violations.push(violation); + }); - angular.forEach(scope.formField.valdrViolations, function (violation) { - scope.violations.push(violation); + if (valdrMessage.angularMessagesEnabled) { + angular.forEach(scope.formField.$error, function (isValid, validatorName) { + if (!valdrUtil.startsWith(validatorName, 'valdr')) { + scope.violations.push(createViolation(validatorName)); + } }); - - if (valdrMessage.angularMessagesEnabled) { - angular.forEach(scope.formField.$error, function (isValid, validatorName) { - if (!valdrUtil.startsWith(validatorName, 'valdr')) { - scope.violations.push(createViolation(validatorName)); - } - }); - } - - scope.violation = scope.violations[0]; - updateTranslations(); - }; - var removeViolationsFromScope = function () { - scope.violations = undefined; - scope.violation = undefined; - }; - - var watchFormFieldErrors = function () { - scope.formField = formController[scope.formFieldName]; - if (scope.formField) { - return { - valdr: scope.formField.valdrViolations, - $error: scope.formField.$error - }; - } - }; + } + + scope.violation = scope.violations[0]; + updateTranslations(); + }; + var removeViolationsFromScope = function () { + scope.violations = undefined; + scope.violation = undefined; + }; + + var watchFormFieldErrors = function () { + scope.formField = formController[scope.formFieldName]; + if (scope.formField) { + return { + valdr: scope.formField.valdrViolations, + $error: scope.formField.$error + }; + } + }; - scope.$watch(watchFormFieldErrors, function () { - if (scope.formField && scope.formField.$invalid) { - addViolationsToScope(); - } else { - removeViolationsFromScope(); - } - }, true); + scope.$watch(watchFormFieldErrors, function () { + if (scope.formField && scope.formField.$invalid) { + addViolationsToScope(); + } else { + removeViolationsFromScope(); + } + }, true); - $rootScope.$on('$translateChangeSuccess', function () { - updateTranslations(); - }); - } - }; - }]); + $rootScope.$on('$translateChangeSuccess', function () { + updateTranslations(); + }); + } + }; + }]); From 93f67e0b831f8ee11548b814677e1086e558b3db Mon Sep 17 00:00:00 2001 From: Marek Matczak Date: Fri, 19 Jun 2015 18:20:49 +0200 Subject: [PATCH 2/3] Description and code snippet of directives enabling valdr validation of non-input/-select/-textarea elements --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 364ef2f..d36cd53 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,29 @@ yourApp.config(function (valdrProvider) { } ``` +## Applying validation to custom input widgets +valdr applies validation to ``````, ```