Disons que j'ai la structure de données (très simple) suivante:
$scope.accounts = [{
percent: 30,
name: "Checking"},
{ percent: 70,
name: "Savings"}];
Ensuite, j'ai la structure suivante dans le cadre d'un formulaire:
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
Maintenant, je veux valider que les pourcentages totalisent 100 pour chaque ensemble de comptes, mais la plupart des exemples que j'ai vus de directives personnalisées ne concernent que la validation d'une valeur individuelle. Quelle est une façon idiomatique de créer une directive qui validerait plusieurs champs dépendants à la fois? Il existe une bonne quantité de solutions pour cela dans jquery, mais je n'ai pas été en mesure de trouver une bonne source pour Angular.
EDIT: J'ai trouvé la directive personnalisée suivante ("share" est synonyme du "pourcentage" du code d'origine). La directive share-validate prend une carte de la forme "{group: accounts, id: $index}"
comme valeur.
app.directive('shareValidate', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
params = angular.copy(scope.$eval(attr.shareValidate));
params.group.splice(params.id, 1);
var sum = +viewValue;
angular.forEach(params.group, function(entity, index) {
sum += +(entity.share);
});
ctrl.$setValidity('share', sum === 100);
return viewValue;
});
}
};
});
Cela PRESQUE fonctionne, mais ne peut pas gérer le cas dans lequel un champ est invalidé, mais une modification ultérieure dans un autre champ le rend à nouveau valide. Par exemple:
Field 1: 61
Field 2: 52
Si je ramène le champ 2 à 39, le champ 2 sera désormais valide, mais le champ 1 n'est toujours pas valide. Des idées?
Ok, les travaux suivants (encore une fois, "partager" est "pour cent"):
app.directive('shareValidate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.shareValidate, function(newArr, oldArr) {
var sum = 0;
angular.forEach(newArr, function(entity, i) {
sum += entity.share;
});
if (sum === 100) {
ctrl.$setValidity('share', true);
scope.path.offers.invalidShares = false;
}
else {
ctrl.$setValidity('share', false);
scope.path.offers.invalidShares = true;
}
}, true); //enable deep dirty checking
}
};
});
Dans le code HTML, définissez l'attribut sur "share-validate" et la valeur sur l'ensemble d'objets que vous souhaitez surveiller.
Vous pouvez vérifier la bibliothèque angularui ( i-utility part). Il a la directive ui-validate.
Vous pouvez alors l'implémenter
<input type="number" name="accountNo" ng-model="account.percent"
ui-validate="{overflow : 'checkOverflow($value,account)' }">
Sur le contrôleur, créez la méthode checkOverflow
qui retourne vrai ou faux en fonction du calcul du compte.
Je n'ai pas essayé cela moi-même mais je veux partager l'idée. Lisez également les échantillons présents sur le site.
J'ai un cas où j'ai un formulaire dynamique où je peux avoir un nombre variable de champs d'entrée sur mon formulaire et j'avais besoin de limiter le nombre de contrôles d'entrée qui sont ajoutés.
Je ne pouvais pas facilement restreindre l'ajout de ces champs de saisie car ils étaient générés par une combinaison d'autres facteurs, j'ai donc dû invalider le formulaire si le nombre de champs de saisie dépassait la limite. J'ai fait cela en créant une référence au formulaire dans mon contrôleur ctrl.myForm
, Puis à chaque fois que les contrôles d'entrée sont générés dynamiquement (dans mon code de contrôleur), je faisais la vérification des limites puis définissais la validité sur le forme comme ceci: ctrl.myForm.$setValidity("maxCount", false);
Cela a bien fonctionné car la validation n'était pas déterminée par un champ de saisie spécifique, mais le nombre total de mes entrées. Cette même approche pourrait fonctionner si vous avez une validation à effectuer qui est déterminée par la combinaison de plusieurs champs.
Pour ma raison
HTML
<form ng-submit="applyDefaultDays()" name="daysForm" ng-controller="DaysCtrl">
<div class="form-group">
<label for="startDate">Start Date</label>
<div class="input-group">
<input id="startDate"
ng-change="runAllValidators()"
ng-model="startDate"
type="text"
class="form-control"
name="startDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="form-group">
<label for="eEndDate">End Date</label>
<div class="input-group">
<input id="endDate"
ng-change="runAllValidators()"
ng-model="endDate"
type="text"
class="form-control"
name="endDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="text-right">
<button ng-disabled="daysForm.$invalid" type="submit" class="btn btn-default">Apply Default Dates</button>
</div>
JS
'use strict';
angular.module('myModule')
.controller('DaysCtrl', function($scope, $timeout) {
$scope.initDate = new Date();
$scope.startDate = angular.copy($scope.initDate);
$scope.endDate = angular.copy($scope.startDate);
$scope.endDate.setTime($scope.endDate.getTime() + 6*24*60*60*1000);
$scope.$watch("daysForm", function(){
//fields are only populated after controller is initialized
$timeout(function(){
//not all viewalues are set yet for somereason, timeout needed
$scope.daysForm.startDate.$validators.checkAgainst = function(){
$scope.daysForm.startDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
$scope.daysForm.endDate.$validators.checkAgainst = function(){
$scope.daysForm.endDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
});
});
$scope.runAllValidators = function(){
//need to run all validators on change
$scope.daysForm.startDate.$validate();
$scope.daysForm.endDate.$validate();
};
$scope.applyDefaultDays = function(){
//do stuff
}
});
Vous pouvez définir une seule directive qui n'est responsable que de cette vérification.
<form>
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
<!-- HERE IT IS -->
<sum-up-to-hundred accounts="accounts"></sum-up-to-hundred>
</form>
Et voici le code de la directive simple.
app.directive('sumUpToHundred', function() {
return {
scope: {
accounts: '<'
},
require: {
formCtrl: '^form'
},
bindToController: true,
controllerAs: '$ctrl',
controller: function() {
var vm = this;
vm.$doCheck = function(changes) {
var sum = vm.accounts.map((a)=> a.percent).reduce((total, n)=> total + n);
if (sum !== 100) {
vm.formCtrl.$setValidity('sumuptohundred', false);
} else {
vm.formCtrl.$setValidity('sumuptohundred', true);
}
};
}
};
});
Ici est un plongeur.