Проверка формы - Требуется один из многих в группе
В проекте, над которым я сейчас работаю, в данный момент у меня есть три текстовых поля, и мне нужно проверить, что хотя бы одно из текстовых полей заполнено.
Я читал в пользовательскую проверку с помощью директив Angular, и я понимаю, что вы можете установить достоверность ввода в функции ссылки директивы, используя следующее:
ctrl.$parsers.unshift(function(viewValue) {
// validation logic here
});
У меня проблема в том, что мне не нужно устанавливать валидность отдельного ввода. Мне нужно аннулировать всю форму, если критерии не выполнены. Мне просто интересно, как к этому подойти?
Я думаю, может быть, мне следует создать директиву, которая помещается в саму вложенную форму, а затем сделать форму недействительной?
Я полагаю, что я просто ищу руководство по поводу того, как мне поступить, потому что мне немного непонятно, с чего начать - кажется, весь материал, который я читаю по пользовательской проверке, относится к тем случаям, когда вы проверяете конкретный вход в отличие от набора условий на форме.
Я надеюсь, что я ясно дал понять! Спасибо..
8 ответов
Вы можете использовать ng-required, чтобы заставить пользователя заполнить хотя бы одно поле, проверив атрибут длины строки.
Например, вы можете сделать следующее:
<form name="myForm">
<input type="text" ng-model="fields.one" name="firstField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<input type="text" name="secondField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" ng-model="fields.two" />
<br/>
<input type="text" ng-model="fields.three" name="thirdField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<button type="submit" ng-disabled="!myForm.$valid">Submit</button>
</form>
Посмотрите этот пример работающей скрипки для более подробной информации.
Читая этот вопрос, вы можете получить более подробную информацию о обязательных и обязательных
Есть несколько подходов, и лучший вариант зависит от ваших конкретных требований.
Вот один подход, который я нашел достаточно общим и гибким.
Под "универсальным" я подразумеваю, что он работает не только для текстовых полей, но и для других видов ввода, таких как флажки.
Он "гибкий", потому что допускает любое количество контрольных групп, так что по крайней мере один элемент управления в каждой группе должен быть непустым. Кроме того, нет "пространственного" ограничения - элементы управления каждой группы могут находиться в любом месте внутри DOM (при необходимости их легко ограничить внутри одного form
).
Подход основан на определении пользовательской директивы (requiredAny
), похожий на ngRequired
, но с учетом других элементов управления в той же группе. После определения директива может использоваться следующим образом:
<form name="myForm" ...>
<input name="inp1" ng-model="..." required-any="group1" />
<input name="inp2" ng-model="..." required-any="group1" />
<input name="inp3" ng-model="..." required-any="group1" />
<input name="inp4" ng-model="..." required-any="group2" />
<input name="inp5" ng-model="..." required-any="group2" />
</form>
В приведенном выше примере, по крайней мере, один из [inp1, inp2, inp3] должен быть непустым, потому что они принадлежат group1
,
То же самое верно для [inp4, inp5], которые принадлежат group2
,
Директива выглядит так:
app.directive('requiredAny', function () {
// Map for holding the state of each group.
var groups = {};
// Helper function: Determines if at least one control
// in the group is non-empty.
function determineIfRequired(groupName) {
var group = groups[groupName];
if (!group) return false;
var keys = Object.keys(group);
return keys.every(function (key) {
return (key === 'isRequired') || !group[key];
});
}
return {
restrict: 'A',
require: '?ngModel',
scope: {}, // An isolate scope is used for easier/cleaner
// $watching and cleanup (on destruction).
link: function postLink(scope, elem, attrs, modelCtrl) {
// If there is no `ngModel` or no groupName has been specified,
// then there is nothing we can do.
if (!modelCtrl || !attrs.requiredAny) return;
// Get a hold on the group's state object.
// (If it doesn't exist, initialize it first.)
var groupName = attrs.requiredAny;
if (groups[groupName] === undefined) {
groups[groupName] = {isRequired: true};
}
var group = scope.group = groups[groupName];
// Clean up when the element is removed.
scope.$on('$destroy', function () {
delete(group[scope.$id]);
if (Object.keys(group).length <= 1) {
delete(groups[groupName]);
}
});
// Update the validity state for the 'required' error-key
// based on the group's status.
function updateValidity() {
if (group.isRequired) {
modelCtrl.$setValidity('required', false);
} else {
modelCtrl.$setValidity('required', true);
}
}
// Update the group's state and this control's validity.
function validate(value) {
group[scope.$id] = !modelCtrl.$isEmpty(value);
group.isRequired = determineIfRequired(groupName);
updateValidity();
return group.isRequired ? undefined : value;
}
// Make sure re-validation takes place whenever:
// either the control's value changes
// or the group's `isRequired` property changes
modelCtrl.$formatters.push(validate);
modelCtrl.$parsers.unshift(validate);
scope.$watch('group.isRequired', updateValidity);
}
};
});
Это может быть не так коротко, но, будучи включенным в модуль, его очень легко интегрировать в ваши формы.
Смотрите также эту (не очень) короткую демонстрацию.
Уже слишком поздно, но, возможно, можно сэкономить время:
Если есть только два поля, и вы хотите сделать одно из них обязательным, то
<input type="text"
ng-model="fields.one"
ng-required="!fields.two" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!fields.one" />
Если у вас есть три, как в вопросе, то
<input type="text"
ng-model="fields.one"
ng-required="!(fields.two || fields.three)" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!(fields.one || fields.three)" />
<br/>
<input type="text"
ng-model="fields.three"
ng-required="!(fields.one|| fields.two)" />
Если больше, чем это, я предложу написать функцию по объему и посмотреть ее.
Модификация ответа ExpertSystem ( /questions/34009442/proverka-formyi-trebuetsya-odin-iz-mnogih-v-gruppe/34009453#34009453), чтобы его код работал в последних версиях angularjs.
я изменил updateValidity(), чтобы установить parse также в true/false
function updateValidity() {
if (group.isRequired) {
modelCtrl.$setValidity('required', false);
modelCtrl.$setValidity('parse', false);
} else {
modelCtrl.$setValidity('required', true);
modelCtrl.$setValidity('parse', true);
}
}
теперь это работает нормально для меня
Столкнулся с этой же проблемой на прошлой неделе; Решение ExpertSystem было хорошим началом, но я искал несколько улучшений:
- Используйте Angular 1.4.3
- Используйте ngMessages
Я в конечном итоге завел этот пример на JSFiddle - надеюсь, это поможет вдохновить других в одной лодке! Соответствующий код JS из скрипки:
var app = angular.module('myApp', ['ngMessages']);
app.controller('myCtrl', function ($scope) {
$scope.sendMessage = function () {
$scope.myForm.$submitted = true;
if ($scope.myForm.$valid) {
alert('Message sent !');
}
};
});
app.directive('requiredAny', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function postLink(scope, elem, attrs, ctrl) {
// If there is no 'ngModel' or no groupName has been specified,
// then there is nothing we can do
if (!ctrl || !attrs.requiredAny) { return };
// If this is the first time we've used this directive in this scope,
// create a section for it's data. If you need / want to make use of
// an isolate scope you'll need to make 'var groups' scoped to the directive;
// but then you may want to look in to clearing out group entries yourself
if (!scope.__requiredAnyGroups) {
scope.__requiredAnyGroups = {}
}
var groups = scope.__requiredAnyGroups;
// Create a bucket for this group if one does not yet exist
if (!groups[attrs.requiredAny]) {
groups[attrs.requiredAny] = {};
}
var group = groups[attrs.requiredAny];
// Create the entry for this control
group[attrs.ngModel] = {
ctrl: ctrl,
hasValue: false
};
ctrl.$validators.requiredAny = function(view, value) {
var thisCtrl = group[attrs.ngModel],
ctrlValue = (typeof value !== 'undefined') && value,
oneHasValue = false;
thisCtrl.hasValue = ctrlValue;
// First determine if any field in the group has a value
for (var prop in group) {
if (group.hasOwnProperty(prop) && group[prop].hasValue) {
oneHasValue = true;
break;
}
}
// Set the validity of all other fields based on whether the group has a value
for (var prop in group) {
if (group.hasOwnProperty(prop) && thisCtrl != group[prop]) {
group[prop].ctrl.$setValidity('requiredAny', oneHasValue);
}
}
// Return the validity of this field
return oneHasValue;
};
}
};
});
У меня было похожее требование группирования в моем проекте, и я написал это. Заинтересованные люди могут использовать это
.directive('group',function(){
return {
require: '^form',
link : function($scope,element,attrs,formCtrl){
var ctrls =[];
element.find(".group-member").each(function(){
var member = angular.element($(this));
var mdlCtrl = member.data("$ngModelController");
if(!mdlCtrl){
throw "Group member should have ng-model";
}
ctrls.push(mdlCtrl);
});
var errKey = attrs['name']+"GrpReqd";
var min = attrs['minRequired'] || 1;
var max = attrs['maxRequired'] || ctrls.length;
$scope.validateGroup = function(){
var defined=0;
for(i=0;i<ctrls.length;i++){
if(ctrls[i].$modelValue){
defined++;
}
}
if(defined < min || defined > max){
formCtrl.$setValidity(errKey,false);
} else {
formCtrl.$setValidity(errKey,true);
}
};
//support real time validation
angular.forEach(ctrls,function(mdlCtrl){
$scope.$watch(function () {
return mdlCtrl.$modelValue;
}, $scope.validateGroup);
});
}
};
})
Использование HTML:
<div name="CancellationInfo" group min-required="1" max-required="1">
<input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField" />
<input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField2" />
<input type="text" class="form-control group-member" style="width:100%;" name="Field2" ng-model="data.myField3" />
</div>
Вот group
Директива идентифицирует логическую группировку. Эта директива находится на элементе без ng-модели, div в приведенном выше примере. group
директива получает 2 необязательных атрибута min-required
а также max-required
, Члены группы идентифицируются с помощью group-member
занятия по отдельным полям. Члены группы должны иметь ng-model
для привязки. поскольку group
директива не имеет ошибки ng-модели будет выпущен в yourForm.$error.CancellationInfoGrpReqd
в вышеуказанном случае. Уникальный ключ ошибки генерируется из имени элемента, на котором group
Директива сидит с GrpReqd
добавлен к нему.
Вот реорганизованный взгляд на большой пост ExpertSystems. Мне не нужен был метод уничтожения, поэтому я его потрошил.
Я также добавил затененное объяснение, которое может помочь в вашем коде. Я использую эту директиву для ВСЕХ моих обязательных полей. То есть, когда я использую эту директиву, я больше не использую ng-required или required.
Если вы хотите, чтобы поле было обязательным, просто введите уникальное имя группы. Если вы не хотите, чтобы поле было обязательным, введите null, а если вы хотите иметь много разных групп, просто передайте соответствующее имя группы.
Я полагаю, что здесь можно сделать немного больше рефакторинга. Angularjs утверждает, что при использовании $ setValidity вместо этого вы должны использовать конвейер $validators, но я не смог заставить это работать. Я все еще учусь этому сложному животному. Если у вас есть больше информации, опубликуйте это!
app.directive('rsPartiallyRequired', function () {
var allinputGroups = {};
return {
restrict: 'A',
require: '?ngModel',
scope: { },
link: function(scope, elem, attrs, ctrl) {
if( !ctrl || !attrs.rsPartiallyRequired ){ return } // no ngModel, or rsPartialRequired is null? then return.
// Initilaize the following on load
ctrl.$formatters.push( validateInputGroup ); // From model to view.
ctrl.$parsers.unshift( validateInputGroup ); // From view to model.
if ( ! allinputGroups.hasOwnProperty( attrs.rsPartiallyRequired )){ // Create key only once and do not overwrite it.
allinputGroups[ attrs.rsPartiallyRequired ] = { isRequired: true } // Set new group name value to { isRequired: true }.
}
scope.inputGroup = allinputGroups[ attrs.rsPartiallyRequired ] // Pass { isRequired: true } to form scope.
function validateInputGroup(value) {
scope.inputGroup[ scope.$id ] = !ctrl.$isEmpty( value ); // Add to inputGroup ex: { isRequired: true, 01E: false }.
scope.inputGroup.isRequired = setRequired( attrs.rsPartiallyRequired ); // Set to true or false.
updateValidity(); // Update all needed inputs based on new user input.
return scope.inputGroup.isRequired ? undefined : value
}
function setRequired(groupName) {
if( ! allinputGroups[ groupName ] ){ return false } // No group name then set required to false.
return Object.keys( allinputGroups[ groupName ] ).every( function( key ) { // Key is 'isRequired' or input identifier.
return ( key === 'isRequired' ) || ! allinputGroups[ groupName ][ key ]
});
}
scope.$watch('scope.inputGroup.isRequired', updateValidity); // Watch changes to inputGroup and update as needed.
function updateValidity() { // Update input state validity when called.
ctrl.$setValidity('required', scope.inputGroup.isRequired ? false : true );
}
}
}
});
// This directive sets input required fields for groups or individual inputs. If an object in the template is given
// to the directive like this:
// Object: { "name": "account_number", "attrs": { "required": { "group": "two" }}}.
// HTML: <input type="text" rs-partially-required="{{ field.attrs.required.group }}" />
// Or anything where the evaluation is a string, for example we could use "groupOne" like this...
// HTML: <input type="text" rs-partially-required="groupOne" />
// Then this directive will set that group to required, even if it's the only member of group.
// If you don't want the field to be required, simply give the directive a null value, like this...
// HTML: <input type="text" rs-partially-required="null" />
// However, when you want to use this directive say in an ngRepeat, then just give it a dynamic string for each input
// and link the inputs together by giving the exact matching string to each group that needs at least one field. ex:
// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="two" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />
// In the above example, the first and fifth input are not required and can be submitted blank.
// The input with group "two" is the only one in the group, so just that input will be required by itself.
// The 2 inputs with "one" will be grouped together and one or the other will require an input before
// the form is valid. The same will be applied with group "three".
// For this form to be valid, group "two" will be required, and 1 input from group one will be required,
// and 1 input from group three will be required before this form can be valid.
Вы можете добавить обязательный атрибут для каждого из них, и, в конце, вы можете положиться на проверку каждого / всех / или только одного из них.
<form name="form" novalidate ng-submit="submit()">
// novalidate is form disabling your browser's own validation mechanism
<input type="text" required ng-model="texts.text1">
<input type="text" required ng-model="texts.text2">
<input type="text" required ng-model="texts.text3">
// you can do validation in variety of ways , but one of them is to disable your submit button until one of the textboxes are filled correctly like this :
<button type="submit" ng-disabled="form.text1.$invalid && form.text2.$invalid && form.text3.$invalid"></button>
</form>
Таким образом, если заполнен только один из них, кнопка будет включена
Я не знаю, как вы будете показывать, что форма недействительна, но я думаю, что удаление кнопки отправки - это общий способ