Проверка формы - требуется один из многих в группе

В проекте, над которым я работаю, в настоящий момент у меня есть три текстовых поля, и мне нужно проверить, что по крайней мере одно из текстовых полей было заполнено.

Я читал в пользовательскую проверку с помощью директив Angular, и я понимаю, что вы можете установить правильность ввода в директивной ссылке, используя следующее:

ctrl.$parsers.unshift(function(viewValue) {
  // validation logic here
});

Проблема заключается в том, что мне не нужно устанавливать индивидуальную достоверность ввода. Мне нужно аннулировать всю форму, если критерии не выполняются. Мне просто интересно, как подойти к этому?

Я думаю, может быть, мне следует создать директиву, которая помещена в прилагаемую форму, а затем сделать форму недействительной?

Я предполагаю, что я просто ищу кое-какие рекомендации о том, как я должен это делать, потому что я немного неясен, с чего начать - весь материал, который я читаю на выборочной проверке, кажется, существует, когда вы проверяете конкретный вход, а не набор условий для формы.

Надеюсь, я поняла! Спасибо..

Ответы

Ответ 1

Вы можете использовать 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>

Подробнее см. рабочий пример скрипта.

Вы можете получить более подробную информацию о требуемом vs ng-required, прочитав этот вопрос

Ответ 2

Существует несколько подходов, и наилучший вариант зависит от ваших точных требований.

Вот один из подходов, который я нашел достаточно общим и гибким.
Под "generic" я подразумеваю, что он работает не только для текстовых полей, но и для других видов входов, таких как флажки.
Он "гибкий", потому что он позволяет любое количество групп управления, так что по крайней мере один элемент управления каждой группы должен быть непустым. Кроме того, нет "пространственного" ограничения - элементы управления каждой группы могут находиться где угодно внутри 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 () {
    // Hash 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 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]);
                }
            });

            // Updates the validity state for the 'required' error-key
            // based on the group status
            function updateValidity() {
                if (group.isRequired) {
                    modelCtrl.$setValidity('required', false);
                } else {
                    modelCtrl.$setValidity('required', true);
                }
            }

            // Updates the group state and this control 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 value changes
            //   or the group `isRequired` property changes
            modelCtrl.$formatters.push(validate);
            modelCtrl.$parsers.unshift(validate);
            scope.$watch('group.isRequired', updateValidity);
        }
    };
});

<суб > Это может быть не так коротким, но после включения в модуль его очень легко интегрировать в ваши формы. Суб >


См. также эту (не так) короткую демонстрацию.

Ответ 3

Это слишком поздно, но может быть, может сэкономить некоторое время:

Если имеется только два поля и требуется сделать одно из них, то

<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)" />

Если это больше, я предлагаю написать функцию в области видимости и посмотреть ее.

См. рабочий пример

Ответ 4

для ответа ExpertSystem (fooobar.com/questions/311145/...), чтобы его код работал в последних угловых символах.

i изменил updateValidity(), чтобы установить синтаксический анализ также на true/false

function updateValidity() {
            if (group.isRequired) {
                modelCtrl.$setValidity('required', false);
                modelCtrl.$setValidity('parse', false); 
            } else {
                modelCtrl.$setValidity('required', true);
                modelCtrl.$setValidity('parse', true);
            }
        }

теперь он работает отлично для меня

Ответ 5

На прошлой неделе возникла эта же проблема; Решение 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 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;
            };
        }
    };
});

Ответ 6

Вы можете добавить обязательный атрибут для каждого из них, и в конце вы можете полагаться на свою валидацию на все/все/или только на одну из них.

        <form name="form" novalidate ng-submit="submit()">
        // novalidate is form disabling your browser 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>

Таким образом, если только один из них заполнен, кнопка будет включена

Я не знаю, как вы собираетесь показывать, что форма недействительна, но я думаю, что создание кнопки отправки является общим способом.

Ответ 7

Вот отредактированный отчет о замечательной публикации ExpertSystems. Мне не нужен метод уничтожения, поэтому я потрошил его.

Я также добавил скрытое объяснение, которое может помочь в вашем коде. Я использую эту директиву для ВСЕХ моих обязательных полей. Значение, когда я использую эту директиву, больше не использую ng-required или требуется.

Если вы хотите, чтобы поле, которое требуется просто передать уникальное имя группы. Если вы не хотите, чтобы требуемое поле передавалось в нуле, и если вы хотите, чтобы многие разные группы просто передавали имя соответствующей группы.

Я считаю, что есть немного больше рефакторинга, которые можно было бы сделать здесь. 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 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.

Ответ 8

У меня было подобное требование группировки в моем проекте, и я написал это. Интересующиеся люди могут использовать этот

.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.