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

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

Я читал в пользовательскую проверку с помощью директив 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>

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

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

Другие вопросы по тегам